package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointGraphEdgeDestination;
import static xj.graph2d.DrawShapeConstants.PointGraphEdgeSource;
import static xj.graph2d.DrawShapeConstants.PointInBounds;
import static xj.graph2d.DrawShapeConstants.PointOnCurve;
import static xj.graph2d.DrawShapeConstants.PointOnCurveCtrPoint;
import static xj.graph2d.DrawShapeConstants.PointOnCurveEndPoint;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.anchorColor;
import static xj.graph2d.DrawShapeConstants.ctrStroke;
import static xj.graph2d.DrawShapeConstants.d;
import static xj.graph2d.DrawShapeConstants.edgeDestinationColor;
import static xj.graph2d.DrawShapeConstants.edgeSourceColor;
import static xj.graph2d.DrawShapeConstants.frameMarkFilled;
import static xj.graph2d.DrawShapeConstants.markStroke;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.QuadCurve2D;

import xj.graph2d.DrawAttr;
import xj.graph2d.DrawShape;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.util.DrawingUtilities;
import xj.util.gui.Point2DFloat;

import static xj.graph2d.graph.Geometry.CHECK;

public class QuadCurveShape 
  extends CurveShapeBase {

  public QuadCurveShape() {
    this(false);
  }

  public QuadCurveShape(boolean createComp) {
    super(createComp);
    ctr1 = new Point2DFloat();
  }

  public QuadCurveShape(float x1, float y1, float x2, float y2, 
			float cx1, float cy1) {
    this(x1, y1, x2, y2, cx1, cy1, false);
  }

  public QuadCurveShape(float x1, float y1, float x2, float y2, 
			float cx1, float cy1, boolean createComp) {
    super(createComp);
    p1.setLocation(x1, y1);
    p2.setLocation(x2, y2);
    // ctr1.setLocation(cx1, cy1);
    ctr1 = new Point2DFloat(cx1, cy1);
    calculateBounds();
  }

  public DrawShape makeInstance() {
    return new QuadCurveShape(comp != null);
  }


  @Override
  public DrawShape cloneDrawShape(boolean deepClone) 
    throws CloneNotSupportedException {
    QuadCurveShape s = new QuadCurveShape((float) p1.getX(), (float) p1.getY(),
					  (float) p2.getX(), (float) p2.getY(), 
					  (float) ctr1.getX(), (float) ctr1.getY(), 
					  comp != null);
    copyTo(s);
    cloneSubshapes(s, deepClone);
    cloneModel(s);
    return s;
  }

  @Override
  public String getName() {
    return "Quadratic curve";
  }

  @Override
  public void moveEnd1Adj(float dx, float dy) {
    p1.setLocation(p1.getX() + dx, p1.getY() + dy);
    curve = null;
    calculateBounds();
  }

  @Override
  public void moveEnd2Adj(float dx, float dy) {
    p2.setLocation(p2.getX() + dx, p2.getY() + dy);
    curve = null;
    calculateBounds();
  }

  @Override
  public void draw(Graphics2D g2) {
    if (head != null || tail != null) {
      setArrowPosition();
      float x1 = (float) p1.getX();
      float y1 = (float) p1.getY();
      float cx = (float) ctr1.getX();
      float cy = (float) ctr1.getY();
      float x2 = (float) p2.getX();
      float y2 = (float) p2.getY();
      double d = Point2D.distance(x1, y1, x2, y2);
      if (head != null) {
	// head.getWidth() < d) {
	head.draw(g2);
	Point2D p = head.getTailPosition();
	x2 = (float) p.getX();
	y2 = (float) p.getY();
      }
      if (tail != null) {
	// tail.getWidth() < d) {
	tail.draw(g2);
	Point2D p = tail.getTailPosition();
	x1 = (float) p.getX();
	y1 = (float) p.getY();
      }
      g2.draw(new QuadCurve2DFloat(x1, y1, cx, cy, x2, y2));
    } else {
      if (curve == null) {
	makeCurve();
      }
      g2.draw(curve);
    }
  }

  @Override
  public void drawOutline(Graphics2D g2, float x1, float y1, float x2, float y2) {
    float cx = (x1 + x2) / 2 + (x2 - x1) / 4;
    float cy = (y1 + y2) / 2 - (y2 - y1) / 4;
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      g2.setColor(attr.getPenColor());
    } else {
      g2.setColor(DrawAttr.getDefaultPenColor());
    }
    g2.draw(new QuadCurve2D.Float(x1, y1, cx, cy, x2, y2));
  }

  @Override
  public void drawFrame(Graphics2D g2) {
    int penSize = 1;
    DrawAttr attr = getDrawAttr();
    if (attr != null) {
      penSize = attr.getPenSize();
    }
    if (ctr1 == null) {
      adjustControlPoints();
    }
    int marksize = DrawingUtilities.getMarkSize(penSize);
    float md = marksize / 2.0f;
    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float cx = (float) ctr1.getX();
    float cy = (float) ctr1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    Color saveColor = g2.getColor();
    Stroke saveStroke = g2.getStroke();

    g2.setColor(anchorColor);
    g2.setStroke(markStroke);
    Ellipse2D e = new Ellipse2D.Float(cx - md, cy - md, marksize, marksize);
    if (frameMarkFilled) { 
      g2.fill(e);
    } else { 
      g2.draw(e);
    }
    g2.setStroke(ctrStroke);
    g2.draw(new Line2D.Float(cx, cy, x1, y1));
    g2.draw(new Line2D.Float(cx, cy, x2, y2));

    g2.setStroke(markStroke);
    g2.setColor(edgeSourceColor);
    e = new Ellipse2D.Float(x1 - md, y1 - md, marksize, marksize);
    if (frameMarkFilled) { 
      g2.fill(e);
    } else { 
      g2.draw(e);
    }

    g2.setColor(edgeDestinationColor);
    e = new Ellipse2D.Float(x2 - md, y2 - md, marksize, marksize);
    if (frameMarkFilled) { 
      g2.fill(e);
    } else { 
      g2.draw(e);
    }
    g2.setColor(saveColor);
    g2.setStroke(saveStroke);
  }

  @Override
  public void setEnds(float x1, float y1, float x2, float y2) {
    p1.setLocation(x1, y1);
    p2.setLocation(x2, y2);
  }

  @Override
  public void setEndsAdj(float x1, float y1, float x2, float y2) {
    p1.setLocation(x1, y1);
    p2.setLocation(x2, y2);
    adjustControlPoints();
    curve = null;
    calculateBounds();
  }

  @Override
  public int isInside(float px, float py) {
    float d1 = d;
    if (attr != null) {
      int penSize = attr.getPenSize();
      if (penSize > 1) {
	d1 += (penSize / 2);
      }
    }
    int result = PointOutside;
    double x1 = bounds.getX();
    double y1 = bounds.getY();
    double x2 = (bounds.getX() + bounds.getWidth());
    double y2 = (bounds.getY() + bounds.getHeight());
    if ((px >= x1 - d1) && 
	(px <= x2 + d1) && 
	(py >= y1 - d1) && 
	(py <= y2 + d1)) {
      result = PointInBounds;
      if (isNearPoint((float) p1.getX(), (float) p1.getY(), px, py)) {
	result = PointOnCurveEndPoint + PointGraphEdgeSource;
      } else if (isNearPoint((float) p2.getX(), (float) p2.getY(), px, py)) {
	result = PointOnCurveEndPoint + PointGraphEdgeDestination;
      } else if (ctr1 != null && 
		 isNearPoint((float) ctr1.getX(), (float) ctr1.getY(), px, py)) {
	result = PointOnCurveCtrPoint;
      //} else if (curve.intersects(px - 2, py - 2, 4, 4)) {
      } else if (isOnCurveBoundary(px, py, d1)) {
	result = PointOnCurve;
      }
    }
    return result;
  }

  @Override
  public void setArrowPosition() {
    if (ctr1 == null) {
      adjustControlPoints();
    }
    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float cx = (float) ctr1.getX();
    float cy = (float) ctr1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    if (head != null) {
      head.setPosition(x2, y2);
      head.setAngle((float) DrawingUtilities.calculateRotationAngle(x2, y2, cx, cy));
    }
    if (tail != null) {
      tail.setPosition(x1, y1);
      tail.setAngle((float) DrawingUtilities.calculateRotationAngle(x1, y1, cx, cy));
    }
  }

  @Override
  public void adjustControlPoints() {
    float x1 = (float) p1.getX();
    float y1 = (float) p1.getY();
    float x2 = (float) p2.getX();
    float y2 = (float) p2.getY();
    float cx = (x1 + x2) / 2 + (x2 - x1) / 4;
    float cy = (y1 + y2) / 2 - (y2 - y1) / 4;
    setCtr1(cx, cy);
    curve = null;
    calculateBounds();
  }

  @Override
  public void adjustEnds(GraphNodeBase src, 
			 GraphNodeBase des, 
			 boolean adjustCtrPts) {

    if (ctr1 == null) {
      adjustControlPoints();
    }

    Point2D q1, q2;
    if (src != null) {
      q1 = src.intersect(ctr1, 
			 tailOffset.dx * src.getShapeWidth(),
			 tailOffset.dy * src.getShapeHeight());
      //CHECK(q1);
    } else {
      q1 = p1;
    }
    if (des != null) {
      q2 = des.intersect(ctr1, 
			 headOffset.dx * des.getShapeWidth(),
			 headOffset.dy * des.getShapeHeight());
      //CHECK(q2);
    } else {
      q2 = p2;
    }

    if (q1 != null && q2 != null) {
      float qx1 = (float) q1.getX();
      float qy1 = (float) q1.getY();
      float qx2 = (float) q2.getX();
      float qy2 = (float) q2.getY();
      if (adjustCtrPts) {
	// keep the control point at the same position relative to the end points
	float cx = (float) ctr1.getX();
	float cy = (float) ctr1.getY();
	float x1 = (float) p1.getX();
	float y1 = (float) p1.getY();
	float x2 = (float) p2.getX();
	float y2 = (float) p2.getY();
	ctr1.setLocation(cx + ((qx1 - x1) + (qx2 - x2)) / 2, 
			 cy + ((qy1 - y1) + (qy2 - y2)) / 2);
      }

      //CHECK(ctr1);
      
      setEnds(qx1, qy1, qx2, qy2);
      // setEnds((float) q1.getX(), (float) q1.getY(), (float) q2.getX(),
      // (float) q2.getY());
    }

    curve = null;
    calculateBounds();
  }

  @Override
  protected void makeCurve() {
    if (curve == null) {
      if (ctr1 == null) {
	adjustControlPoints();
      }
      float x1 = (float) p1.getX();
      float y1 = (float) p1.getY();
      float cx = (float) ctr1.getX();
      float cy = (float) ctr1.getY();
      float x2 = (float) p2.getX();
      float y2 = (float) p2.getY();

      //CHECK(p1);
      //CHECK(p2);
      //CHECK(ctr1);

      curve = new QuadCurve2DFloat(x1, y1, cx, cy, x2, y2);
    }
  }

}
